package com.green.home.auth.config;

import com.green.home.auth.support.bean.CustomOAuth2TokenCustomizer;
import com.green.home.auth.support.grant.password.OAuth2AuthorizationPasswordAuthenticationConverter;
import com.green.home.auth.support.grant.password.OAuth2AuthorizationPasswordAuthenticationProvider;
import com.green.home.auth.support.handle.GreenAuthenticationFailureHandler;
import com.green.home.auth.support.handle.GreenAuthenticationSuccessHandler;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

import java.util.List;
import java.util.function.Consumer;

/**
 * 认证服务配置类
 * @author limingliang
 * @date 2024/3/6 15:16
 */
@Configuration
@RequiredArgsConstructor
public class AuthorizationConfiguration {

    private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";
    private final OAuth2AuthorizationService authorizationService;

    /**
     * 认证配置
     * OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http)
     * 源码中配置了oauth2的默认端口如：/oauth2/introspect、/oauth2/authorize等端口设置必须认证后才能访问
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                // 认证信息存储设置
                .authorizationService(authorizationService)
                // 设置获取token失败时异常返回或成功时token保存方式
                .tokenEndpoint(tokenEndpoint -> tokenEndpoint
                        .accessTokenRequestConverters(accessTokenRequestConvertersConsumer())
                        .authenticationProviders(authenticationProvidersConsumer())
                        .accessTokenResponseHandler(new GreenAuthenticationSuccessHandler())
                        .errorResponseHandler(new GreenAuthenticationFailureHandler()))
                // token生成配置内容，扩展token中解析出用户信息
                .tokenGenerator(tokenGenerator())
                // 携带客户端认证标识，解析报错提示内容，client_secret_basic类型获取token时会先调用
                .clientAuthentication(authentication -> authentication
                        .errorResponseHandler(new GreenAuthenticationFailureHandler()))
                // 授权端点配置
                .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint
                        .consentPage(CUSTOM_CONSENT_PAGE_URI));

        http.exceptionHandling((exceptions) -> exceptions
                .defaultAuthenticationEntryPointFor(
                        new LoginUrlAuthenticationEntryPoint("/login"),
                        new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                )
        );
        return http.build();
    }

    /**
     * 设置令牌生成规则及用户自定义扩展类型
     */
    @Bean
    public OAuth2TokenGenerator<?> tokenGenerator() {
        OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
        accessTokenGenerator.setAccessTokenCustomizer(new CustomOAuth2TokenCustomizer());
        OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
        return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, refreshTokenGenerator);
    }

    private Consumer<List<AuthenticationConverter>> accessTokenRequestConvertersConsumer() {
        return converters -> converters.add(new OAuth2AuthorizationPasswordAuthenticationConverter());
    }

    private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer() {
        return providers -> providers.add(new OAuth2AuthorizationPasswordAuthenticationProvider(authorizationService, tokenGenerator()));
    }
}
